home *** CD-ROM | disk | FTP | other *** search
/ Night Owl 6 / Night Owl's Shareware - PDSI-006 - Night Owl Corp (1990).iso / 015a / swap300.zip / SWAP.ASM < prev    next >
Assembly Source File  |  1990-10-04  |  86KB  |  1,875 lines

  1. page 60, 132
  2.  
  3. ;   SWAP.ASM        Version 3.00    October 4, 1990
  4. ;
  5. ;   Contains code and data needed to swap most of the current program out
  6. ;   to extended memory, expanded memory, or disk; execute another program;
  7. ;   and re-load the original program back into memory.
  8. ;
  9. ;   Copyright (C) 1990 by Marty Del Vecchio
  10. ;   Released to the public domain for free use by all
  11. ;   Product is supplied as is and author disclaims all warranties,
  12. ;   explicit or implied, about the functionality of the source code
  13. ;   or object modules supplied, and shall not be held responsible
  14. ;   for any damages caused by use of product.
  15. ;
  16. ;   Code to parse default FCB's written and generously donated
  17. ;   by David E. Jenkins (jenkins@wang.com or dave.jenkins@office.wang.com).
  18. ;
  19. ;   Contributions not solicited.  Just appreciate the fact that somebody
  20. ;   took the time to write and comment this code.  If you have any
  21. ;   questions, please contact me at:
  22. ;
  23. ;   Marty Del Vecchio                   Channel 1 BBS
  24. ;   99 Marlboro Road                    Boston, MA
  25. ;   Southborough, MA  01772             (617) 354-8873
  26. ;   (508) 485-9718
  27. ;
  28. ;   internet:  marty@bsn.mceo.dg.com
  29. ;
  30. ;   For information about the contents of this file, see the accompanying
  31. ;   file SWAP.DOC.
  32. ;
  33.  
  34.  
  35. ; 'DOSSEG' gives us support for Microsoft C and Turbo C segment naming
  36. ; and ordering schemes
  37. DOSSEG
  38.  
  39. ; Figure out which memory model we're assembling for.  Specified on
  40. ;  MASM command line with /D followed by either _Small, _Compact, _Medium,
  41. ;  or _Large.  If none specified, _Small is assumed.
  42.  
  43. ; Once the model is defined, MASM provides two definitions, @codesize
  44. ;  and @datasize, to determine the size of code and data pointers.  If
  45. ;  @codesize is 0 (Small and Compact), there is one code segment, and
  46. ;  code addresses are 16 bits (offset only).  If @codesize is 1 (Medium
  47. ;  and Large), there are multiple code segments, and code addresses are
  48. ;  32 bits (segment and offset).  Similarly, @datasize of 0 means one
  49. ;  data segment (16-bit pointers), and @datasize of 1 means multiple
  50. ;  data segments (32-bit pointers).
  51.  
  52. IFDEF _large
  53.    .MODEL Large, C
  54.    IF1
  55.       %out Assembling for C, Large memory model
  56.    ENDIF
  57. ELSE
  58.    IFDEF _compact
  59.       .MODEL Compact, C
  60.       IF1
  61.          %out Assembling for C, Compact memory model
  62.       ENDIF
  63.    ELSE
  64.       IFDEF _medium
  65.          .MODEL Medium, C
  66.          IF1
  67.             %out Assembling for C, Medium memory model
  68.          ENDIF
  69.       ELSE
  70.          .MODEL Small, C
  71.          IF1
  72.             %out Assembling for C, Small memory model
  73.          ENDIF
  74.       ENDIF
  75.    ENDIF
  76. ENDIF
  77.  
  78. ; Report whether multiple DOS memory blocks will be swapped
  79. IF1
  80.    IFDEF NOFRAG
  81.       %out Multiple DOS memory blocks will NOT be swapped
  82.    ELSE
  83.       %out Multiple DOS memory blocks will be swapped
  84.    ENDIF
  85. ENDIF
  86.  
  87. ; Figure out which save method we are using--EMS, XMS, disk, or a
  88. ;  combination.
  89.  
  90. ; Specified on MASM command line with /D followed by either "xms", "ems",
  91. ;  "disk", or "all".  For example, to create a swap() that will try using
  92. ;  XMS and EMS, you would use "masm swap.asm /Dems /Dxms".
  93.  
  94. ; If none specified, it will use all.  To change the order in which swap()
  95. ;  attempts to save the program to different places, see the function
  96. ;  save_program below.
  97.  
  98. ; First, see if they want all of them...
  99. IFDEF all
  100.    USE_DISK  EQU 1
  101.    USE_XMS   EQU 1
  102.    USE_EMS   EQU 1
  103. ELSE
  104.    ; /Dall not specified--try each individually...
  105.    IFDEF disk
  106.       USE_DISK  EQU 1
  107.    ENDIF
  108.  
  109.    IFDEF xms
  110.       USE_XMS   EQU 1
  111.     ENDIF
  112.  
  113.    IFDEF ems
  114.       USE_EMS   EQU 1
  115.    ENDIF
  116.  
  117. ENDIF
  118.  
  119. ; Now see if they declared anything--if not, it will use them all
  120. IFNDEF USE_DISK
  121.    IFNDEF USE_EMS
  122.       IFNDEF USE_XMS
  123.          USE_DISK  EQU 1
  124.          USE_XMS   EQU 1
  125.          USE_EMS   EQU 1
  126.       ENDIF
  127.    ENDIF
  128. ENDIF
  129.  
  130. ; Constant definitions for easier reading
  131. STDERR          equ     2           ; Standard DOS file handle for error output
  132. GET_VECTOR      equ     35h         ; DOS function to get interrupt vector
  133. EMM_INT         equ     67h         ; EMS interrupt vector
  134. EMM_NAME_LEN    equ     8           ; Length of EMS device driver name
  135. MAX_DOS_CMD     equ     127         ; Maximum DOS command-line length
  136.  
  137. ; If we will swap out all DOS memory blocks a program owns, we need a
  138. ;   place to store information about them
  139. MAX_EXTRA       equ     16          ; Maximum number of extra DOS allocation blocks to swap
  140.  
  141. dos_block       struc               ; Structure for extra DOS memory blocks
  142. block_seg       dw      0           ; User's segment address of block
  143. block_size      dw      0           ; Size in paragraphs of block
  144. dos_block       ends
  145.  
  146.  
  147. bptr            equ     byte ptr    ; Means we're loading/storing 8 bits
  148. wptr            equ     word ptr    ; Means we're loading/storing 16 bits
  149. dptr            equ     dword ptr   ; Means we're loading/storing 32 bits
  150.  
  151.  
  152. ; All code and data must be in the code segment, which is the first segment
  153. ;  in all Turbo C, Turbo C++, and Microsoft C memory models.
  154.  
  155. ; If we are in the Medium or Large models, there are multiple code segments.
  156. ;  If this is the case, our default code segment name will be "SWAP_TEXT".
  157. ;  This is acceptable in most cases, except when using the Turbo C integrated
  158. ;  development environment.  See SWAP.DOC for details.
  159.  
  160. ; If you are using Turbo C's Integrated Development Environment, the line
  161. ;  right after "IF @codesize" MUST say ".CODE  _TEXT"!!!!!!!!!!!!!!!!!!!!
  162. IF @codesize
  163. .CODE   _TEXT
  164. ELSE
  165. .CODE
  166. ENDIF
  167.  
  168. ; *****************************************************************************
  169. ; Our resident data declarations--this data will be needed after the swap
  170. ;  has occurred, and thus must be above the resident line
  171. ; *****************************************************************************
  172.  
  173. ; *****************************************************************************
  174. ; First, all variables that will be used by all versions assembled from
  175. ; this source file, regardless of what save options are selected
  176. ; *****************************************************************************
  177. ret_code    dw      0           ; Return code (to C caller) of this swap routine
  178.                                 ;   0 = success
  179.                                 ;   1 = unable to shrink DOS memory allocation
  180.                                 ;   2 = unable to save program to EMS
  181.                                 ;   3 = unable to execute requested program
  182.                                 ; These values must be the same as those listed
  183.                                 ;  in SWAP.H!!!!!!!!!
  184.  
  185. ; *****************************************************************************
  186. ; Variables that deal with DOS' memory allocation blocks
  187. old_size    dw      0           ; The old size (in paragraphs) of this program
  188. new_size    dw      0           ; The new "resident" size, doesn't include code/data swapped
  189. prog_size   dw      0           ; Size in paragraphs of saved part of program block (old_size - new_size)
  190. total_paras dw      0           ; Size (in paragraphs) of all blocks combined
  191. my_psp      dw      0           ; This program's Program Segment Prefix (PSP)
  192. mcb_psp     dw      0           ; The PSP address in this program's memory block
  193. start_seg   dw      0           ; Segment address of released memory
  194.  
  195. ; If we are swapping all DOS memory blocks a program owns, we store
  196. ;  them in this array of structures
  197. IFNDEF NOFRAG
  198. extra_count dw      0           ; # of extra blocks to save (not including program block)
  199. dos_blocks  dos_block MAX_EXTRA dup (<>)    ; Array for extra blocks
  200. ENDIF
  201. ; *****************************************************************************
  202.  
  203. ; *****************************************************************************
  204. ; Variable used during the save/restore process
  205. handle      dw      0           ; EMS/XMS/disk file handle
  206. ; *****************************************************************************
  207.  
  208. ; *****************************************************************************
  209. ; A temporary stack in our code segment, and associated variables
  210. old_sp      dw      0               ; Place to save this program's stack
  211. old_ss      dw      0               ;  information while executing new program
  212.  
  213. ; XMS driver needs a large stack (at least 256 bytes free when called)
  214. IFDEF USE_XMS
  215. new_stack   db      320 dup ('?')   ; Temporary stack we can address after swap
  216. ELSE
  217. new_stack   db      128 dup ('?')   ; Temporary stack we can address after swap
  218. ENDIF
  219. new_sp      label   word            ; Point SP to "top" of stack
  220. ; *****************************************************************************
  221.  
  222. ; *****************************************************************************
  223. ; Variables that deal with the execution of the new program
  224. prog_name   db      128 dup (0)     ; Storage for name of program to execute
  225. cmd_pad     db      0               ; Maintain word-alignment for variables
  226. cmd_len     db      0               ; Storage for length of command line parameters
  227. cmd_line    db      128 dup (0)     ; Storage for command line parameters
  228.  
  229. param_blk   label   byte            ; Program Parameter Block--pass to DOS on exec call
  230. env_seg     dw      0               ; Environment segment address, 0 means a COPY of ours
  231. cmd_ofs     dw      offset @code:cmd_len    ; Offset address of command line
  232. cmd_seg     dw      seg cmd_line    ; Segment address of command line
  233. fcb_5C_ofs  dw      offset fcb5C    ; Far pointers to default FCB's.  Some
  234. fcb_5C_seg  dw      seg fcb5C       ;  programs (such as DOS' CHKDSK.COM)
  235. fcb_6C_ofs  dw      offset fcb6C    ;  depend on these being parsed from
  236. fcb_6C_seg  dw      seg fcb6C       ;  the command line before the EXEC call
  237. ; *****************************************************************************
  238.  
  239. ; *****************************************************************************
  240. ; Variables needed to parse the command line into the default FCB's
  241. c_l_length  dw      0               ; Command line length
  242. si_5C       dw      0               ; Save area for pointer to cmd line arg 1
  243. si_6C       dw      0               ; Save area for pointer to cmd line arg 2
  244.  
  245. ; Default FCB to be passed to PSP offset 5C (hex)
  246. fcb5C       label   byte
  247. fcb5C_drive db      0               ; drive
  248. fcb5C_fname db      8 dup (?)       ; file name
  249. fcb5C_ext   db      3 dup (?)       ; extension
  250. fcb5C_pad   db      4 dup (?)       ; unused
  251.  
  252. ; Default FCB to be passed to PSP offset 6C (hex)
  253. fcb6C       label   byte
  254. fcb6C_drive db      0               ; drive
  255. fcb6C_fname db      8 dup (?)       ; file name
  256. fcb6C_ext   db      3 dup (?)       ; extension
  257. fcb6C_pad   db      4 dup (?)       ; unused
  258. ; *****************************************************************************
  259.  
  260. exec_ret    db      0               ; Return code from executed program
  261. exec_pad    db      0               ; Maintain word-alignment for variables
  262. restore_proc dw     0               ; Address of appropriate restore routine
  263.  
  264. ; *****************************************************************************
  265. ; Message to display to screen when we can't reload program
  266. abort_msg   db      0dh, 0ah, 'SWAP: Unable to reload program.', 0dh, 0ah
  267. abort_len   dw      $ - offset @code:abort_msg
  268. ; *****************************************************************************
  269.  
  270. ; *****************************************************************************
  271. ; Next, the variables needed only for certain versions of the routine,
  272. ;  depending on which save/restore options are chosen
  273. ; *****************************************************************************
  274.  
  275. ; *****************************************************************************
  276. ; Variables needed only when swapping to XMS
  277. IFDEF USE_XMS
  278. XMS_proc    dd      0               ; Address of XMS entry point
  279.  
  280. XMS_struc       label   byte        ; Structure needed to move memory with XMS
  281. XMS_size        dd      0           ; # of bytes to move (must be even)
  282. XMS_from        dw      0           ; Handle of source, 0=conventional memory
  283. XMS_from_addr   dd      0           ; Address of source memory
  284. XMS_to          dw      0           ; Handle of destionation, 0=conventional memory
  285. XMS_to_addr     dd      0           ; Address of destination memory
  286. ENDIF
  287. ; *****************************************************************************
  288.  
  289. ; *****************************************************************************
  290. ; Variables needed only when swapping to EMS
  291. IFDEF USE_EMS
  292. pages_used  db      0           ; # of pages of EMS used
  293. emm_name    db      'EMMXXXX0'  ; Name of EMS device driver
  294.  
  295. EMS_struc   label   byte        ; Structure needed to move memory with EMS 4.0+
  296. EMS_size    dd      0           ; # of bytes to move
  297. EMS_from    db      0           ; Type of source memory (0 = conventional, 1 = expanded)
  298. EMS_from_h  dw      0           ; Source memory handle (0 = conventional)
  299. EMS_from_o  dw      0           ; Offset of source memory (expanded = 0-16K, conventional = 0-64K)
  300. EMS_from_s  dw      0           ; Segment/page of source (expanded = logical page, conventional = segment)
  301. EMS_to      db      0           ; Type of desination memory (0 = conventional, 1 = expanded)
  302. EMS_to_h    dw      0           ; Destination memory handle (0 = conventional)
  303. EMS_to_o    dw      0           ; Offset of destination memory (expanded = 0-16K, conventional = 0-64K)
  304. EMS_to_s    dw      0           ; Segment/page of destination (expanded = logical page, conventional = segment)
  305.  
  306. ems_offset  dd      0           ; Destination pointer--absolute byte offset into handle
  307. ENDIF
  308. ; *****************************************************************************
  309.  
  310. ; *****************************************************************************
  311. ; Variables needed only when swapping to disk
  312. IFDEF USE_DISK
  313. fname       db      80 dup (0)  ; Name of the file data is saved to/read from
  314. paras_left  dw      0           ; temporary counter
  315. ENDIF
  316. ; *****************************************************************************
  317.  
  318.  
  319.  
  320. ; *****************************************************************************
  321. ; Version-dependent code--only assemble the routine to restore the program
  322. ; from each media (XMS, EMS, disk) if it was specified on the command line
  323. ; *****************************************************************************
  324.  
  325.  
  326. ; *****************************************************************************
  327. ; restore_xms   Attempts to restore program from XMS extended memory
  328. ;
  329. ; Entry:        DS points to our variables
  330. ;               Program was saved to XMS extended memory (block referred to by handle)
  331. ;
  332. ; Return:       Carry set on error, carry clear on success
  333. ; *****************************************************************************
  334. IFDEF USE_XMS
  335. restore_xms     proc    near
  336.                 push    es
  337.  
  338.                 assume  ds:@code                    ; Tell MASM that DS points to our variables
  339.  
  340. ; First, attempt to restore the portion of the program block that was saved
  341. xms_prog_rest:  mov     ax, wptr start_seg          ; Released segment address
  342.                 mov     es, ax
  343.                 mov     ax, wptr prog_size          ; Size (in paragraphs)
  344.  
  345.                 xor     bx, bx
  346.                 mov     wptr XMS_from_addr, bx      ; Initialize XMS source
  347.                 mov     wptr XMS_from_addr + 2, bx  ;  address (offset into extended memory block)
  348.  
  349.                 call    rest_xms_seg                ; Attempt to restore it
  350.  
  351. IFNDEF NOFRAG
  352.                 jc      xms_dealloc                 ; Carry set = error, exit
  353.  
  354. ; Next, restore the extra DOS segments
  355. xms_extra_rest: mov     cx, wptr extra_count    ; Number of extra blocks to save
  356.                 jcxz    xms_dealloc             ; If CX = 0, we exit routine
  357.  
  358.                 mov     di, offset dos_blocks   ; DI -> array of segment/size pairs
  359.  
  360. xms_extra_rest_loop:
  361.                 mov     ax, wptr [di].block_seg
  362.                 mov     es, ax                  ; ES = segment to restore
  363.                 mov     ax, wptr [di].block_size; AX = size in paragraphs
  364.                 push    cx
  365.                 push    di
  366.                 call    rest_xms_seg            ; Attempt to restore this block
  367.                 pop     di
  368.                 pop     cx
  369.                 jc      xms_dealloc             ; Carry flag set == error, exit
  370.                 add     di, size dos_block
  371.                 loop    xms_extra_rest_loop     ; Keep going through all blocks
  372.  
  373. ENDIF
  374.  
  375. xms_dealloc:    rcl     bl, 1                   ; Save carry flag in low bit of bl
  376.  
  377.                 mov     dx, wptr handle         ; First, free XMS handle
  378.                 mov     ah, 0Ah
  379.                 push    bx
  380.                 call    dptr XMS_proc
  381.                 pop     bx
  382.  
  383.                 rcr     bl, 1                   ; Restore carry flag from bl low bit
  384.  
  385. restore_xms_ret:pop     es
  386.                 ret
  387. restore_xms     endp
  388.  
  389.  
  390. ; *****************************************************************************
  391. ; rest_xms_seg  Attempts to restore a chunk of RAM from XMS memory
  392. ;
  393. ; Entry:        ES points to the segment to restore
  394. ;               AX contains its length (in paragraphs)
  395. ;               handle holds the XMS handle to read from
  396. ;               XMS_from_addr contains offset into extended memory for read
  397. ;
  398. ; Return:       Carry set on error, carry clear on success
  399. ;               Updates XMS_from_addr for next read
  400. ; *****************************************************************************
  401. rest_xms_seg    proc    near
  402.                 push    ds
  403.                 push    es
  404.  
  405. ; Call the XMS copy memory function to do this; fill in request block
  406. xms_read_size:  mov     bx, 10h                     ; AX = # of paragraphs, convert to bytes
  407.                 mul     bx                          ; DX:AX = AX * 10h, # of bytes to read
  408.                 mov     wptr XMS_size, ax           ; Store # of bytes to read
  409.                 mov     wptr XMS_size + 2, dx
  410.  
  411. xms_read_from:  mov     ax, wptr handle             ; Source XMS handle
  412.                 mov     wptr XMS_from, ax           ;  XMS_from_addr already filled in
  413.  
  414. xms_read_to:    xor     bx, bx
  415.                 mov     wptr XMS_to, bx             ; Read into conventional memory
  416.                 mov     wptr XMS_to_addr, bx        ; Offset of dest address
  417.                 mov     ax, es                      ; Segment of destination address
  418.                 mov     wptr XMS_to_addr + 2, ax
  419.  
  420. do_xms_read:    mov     si, offset @code:XMS_struc  ; DS:SI -> XMS structure
  421.                 mov     ah, 0Bh
  422.                 call    dptr XMS_proc               ; Do the move
  423.                 cmp     ax, 1
  424.                 jnz     rest_xms_seg_er
  425.  
  426. rest_xms_seg_ok:mov     ax, wptr XMS_size           ; Retrieve length
  427.                 mov     dx, wptr XMS_size + 2       ;  (32 bits)
  428.                 add     wptr XMS_from_addr, ax      ; Add two 32-bit values
  429.                 adc     wptr XMS_from_addr + 2, dx  ; Update XMS read pointer
  430.                 clc                                 ; Signal success
  431.                 jmp     short rest_xms_seg_ret
  432.  
  433. rest_xms_seg_er:stc
  434.  
  435. rest_xms_seg_ret:
  436.                 pop     es
  437.                 pop     ds
  438.                 ret
  439. rest_xms_seg    endp
  440.  
  441. ENDIF
  442. ; *****************************************************************************
  443.  
  444.  
  445. ; *****************************************************************************
  446. ; restore_ems   Attempts to restore program from EMS expanded memory
  447. ;
  448. ; Entry:        DS points to our variables
  449. ;               Program was saved to EMS expanded memory (block referred to by handle)
  450. ;
  451. ; Return:       Carry set on error, carry clear on success
  452. ; *****************************************************************************
  453. IFDEF USE_EMS
  454. restore_ems     proc    near
  455.                 push    es
  456.  
  457.                 assume  ds:@code                    ; Tell MASM that DS points to our variables
  458.  
  459. ; First, attempt to restore the portion of the program block that was saved
  460. ems_prog_rest:  mov     ax, wptr start_seg          ; Released segment address
  461.                 mov     es, ax
  462.                 mov     ax, wptr prog_size          ; Size (in paragraphs)
  463.  
  464.                 xor     bx, bx
  465.                 mov     wptr ems_offset, bx         ; Maintain absolute by offset
  466.                 mov     wptr ems_offset + 2, bx     ;  pointer relative to handle
  467.  
  468.                 call    rest_ems_seg                ; Attempt to restore it
  469.  
  470. IFNDEF NOFRAG
  471.                 jc      ems_dealloc                 ; Carry set = error, exit
  472.  
  473. ; Next, restore the extra DOS segments
  474. ems_extra_rest: mov     cx, wptr extra_count    ; Number of extra blocks to save
  475.                 jcxz    ems_dealloc             ; If CX = 0, we exit routine
  476.  
  477.                 mov     di, offset dos_blocks   ; DI -> array of segment/size pairs
  478.  
  479. ems_extra_rest_loop:
  480.                 mov     ax, wptr [di].block_seg
  481.                 mov     es, ax                  ; ES = segment to restore
  482.                 mov     ax, wptr [di].block_size; AX = size in paragraphs
  483.                 push    cx
  484.                 push    di
  485.                 call    rest_ems_seg            ; Attempt to restore this block
  486.                 pop     di
  487.                 pop     cx
  488.                 jc      ems_dealloc             ; Carry flag set == error, exit
  489.                 add     di, size dos_block
  490.                 loop    ems_extra_rest_loop     ; Keep going through all blocks
  491.  
  492. ENDIF
  493.  
  494. ems_dealloc:    rcl     bl, 1                   ; Save carry flag in low bit of bl
  495.  
  496.                 mov     ah, 45h                 ; Deallocate EMS memory
  497.                 mov     dx, wptr handle         ; Specify which handle
  498.                 push    bx
  499.                 int     67h
  500.                 pop     bx
  501.  
  502.                 rcr     bl, 1                   ; Restore carry flag from bl low bit
  503.  
  504. restore_ems_ret:pop     es
  505.                 ret
  506. restore_ems     endp
  507.  
  508. ; *****************************************************************************
  509. ; rest_ems_seg  Attempts to restore a chunk of RAM from EMS memory
  510. ;
  511. ; Entry:        ES points to the segment to restore
  512. ;               AX contains its length (in paragraphs)
  513. ;               handle holds the EMS handle to write to
  514. ;               ems_offset holds the 32-bit absolute offset in expanded
  515. ;                memory to read this block from
  516. ;
  517. ; Return:       Carry set on error, carry clear on success
  518. ;               Updates ems_offset with proper offset for next read
  519. ; *****************************************************************************
  520. rest_ems_seg    proc    near
  521.                 push    ds
  522.                 push    es
  523.  
  524.                 assume  ds:@code                ; Tell MASM DS points to our variables
  525.  
  526. ; Call the EMS copy memory function to do this; fill in the EMS request block
  527. ems_read_size:  mov     bx, 10h                     ; AX = # of paragraphs
  528.                 mul     bx                          ; DX:AX = AX * 10h, convert paragraphs to bytes
  529.                 mov     wptr EMS_size, ax           ; Store # of bytes to write
  530.                 mov     wptr EMS_size + 2, dx
  531.  
  532. ems_read_to:    xor     bx, bx
  533.                 mov     bptr EMS_to, bl             ; Copying to conventional memory (0)
  534.                 mov     wptr EMS_to_h, bx           ; Destination handle is 0 (conventional memory)
  535.                 mov     wptr EMS_to_o, bx           ; Destination offset is 0
  536.                 mov     ax, es                      ; Segment of destination address is ES
  537.                 mov     wptr EMS_to_s, ax
  538.  
  539. ems_read_from:  mov     bptr EMS_from, 1            ; Copying to expanded memory
  540.                 mov     ax, wptr handle
  541.                 mov     wptr EMS_from_h, ax         ; Specify EMS handle
  542.  
  543.                 ; 32-bit absolute offset for copy is in ems_offset
  544.                 ;  convert to EMS page:offset (16K pages) values
  545.                 mov     ax, wptr ems_offset         ; Load 32-byte offset
  546.                 mov     dx, wptr ems_offset + 2
  547.                 mov     bx, ax                      ; Save a copy of ax (low 16 bits)
  548.                 and     ax, 0011111111111111b       ; Get (ax & (16K - 1)), this is the offset (14 bits)
  549.                 mov     wptr EMS_from_o, ax         ; Save page offset
  550.                 mov     cl, 14
  551.                 shr     bx, cl                      ; Move low 2 bits of page into low 2 bits of bx
  552.                 mov     cl, 2
  553.                 shl     dx, cl                      ; Move hi ? bits of page into dx shl 2
  554.                 or      dx, bx                      ; DX = page number (combine two values)
  555.                 mov     wptr EMS_from_s, dx         ; Save
  556.  
  557.                 mov     ax, wptr EMS_size           ; Retrieve size of copy
  558.                 mov     dx, wptr EMS_size + 2
  559.                 add     wptr ems_offset, ax         ; Update EMS copy pointer
  560.                 adc     wptr ems_offset + 2, dx     ;  for next EMS write
  561.  
  562. do_ems_read:    mov     si, offset @code:EMS_struc  ; DS:SI -> EMS request structure
  563.                 mov     ax, 5700h                   ; Function 57 (copy/exchange memory), sub 0, copy memory
  564.                 int     67h                         ; Call EMS manager
  565.                 or      ah, ah                      ; AH = 0 means success
  566.                 jnz     rest_ems_seg_er             ; Not 0 means error
  567.  
  568. rest_ems_seg_ok:clc                                 ; Signal success
  569.                 jmp     short rest_ems_seg_ret
  570.  
  571. rest_ems_seg_er:stc
  572.  
  573. rest_ems_seg_ret:
  574.                 pop     es
  575.                 pop     ds
  576.                 ret
  577. rest_ems_seg    endp
  578.  
  579. ENDIF
  580. ; *****************************************************************************
  581.  
  582.  
  583. ; *****************************************************************************
  584. ; restore_disk  Attempts to restore program from DOS disk file
  585. ;
  586. ; Entry:        DS points to our code segment
  587. ;               Program was saved to DOS disk file (full path stored in fname)
  588. ;
  589. ; Return:       Carry set on error, carry clear on success
  590. ; *****************************************************************************
  591. IFDEF USE_DISK
  592. restore_disk    proc    near
  593.  
  594.                 push    ds
  595.  
  596.                 assume  ds:@code                ; Tell MASM that DS points to our variables
  597.  
  598. open_file:      mov     dx, offset @code:fname  ; DS:DX -> file name
  599.                 mov     ax, 3D42h               ; DOS function 3Dh, open file
  600.                                                 ;  al = open for read only, deny none
  601.                 int     21h                     ; Call DOS
  602.                 jnc     open_ok                 ; Carry clear = all OK
  603.                 jmp     short restore_disk_ret  ; Carry set, just exit with error
  604.  
  605. open_ok:        mov     wptr handle, ax         ; File handle returned from DOS
  606.  
  607. ; First, restore the program block contents saved to disk
  608. disk_prog_rest: mov     ax, wptr start_seg      ; Get segment of program block saved
  609.                 mov     es, ax
  610.                 mov     ax, wptr prog_size      ; Get size of program block saved
  611.                 call    rest_disk_seg           ; Try to restore it
  612.                 jc      restore_disk_er         ; Carry set == error
  613.  
  614. IFNDEF NOFRAG
  615. ; Next, restore the contents of the extra blocks saved to disk
  616. disk_extra_rest:
  617.                 mov     cx, wptr extra_count    ; Number of extra blocks to restore
  618.                 jcxz    close_read              ; IF CX = 0, we're done restoring
  619.  
  620.                 mov     di, offset dos_blocks   ; DI -> array of segment/size pairs
  621.  
  622. disk_extra_rest_loop:
  623.                 mov     ax, wptr [di].block_seg
  624.                 mov     es, ax                  ; ES = segment to restore to
  625.                 mov     ax, wptr [di].block_size; AX = size in paragraphs
  626.                 push    cx
  627.                 push    di
  628.                 call    rest_disk_seg           ; Attempt to restore this block
  629.                 pop     di
  630.                 pop     cx
  631.                 jc      restore_disk_er         ; Error--exit routine
  632.                 add     di, size dos_block
  633.                 loop    disk_extra_rest_loop    ; Look for next DOS block
  634.  
  635. ENDIF
  636.  
  637. close_read:     mov     ah, 3Eh                 ; Close file
  638.                 int     21h                     ; Call DOS
  639.  
  640. restore_disk_ok:clc                             ; Signal success
  641.                 jmp     short restore_disk_ret  ;  and Exit
  642.  
  643. restore_disk_er:
  644.                 mov     ah, 3Eh                 ; Error, close file first
  645.                 int     21h                     ; Call DOS
  646.                 stc                             ; Signal failure
  647.  
  648. restore_disk_ret:
  649.                 pop     ds                      ; Restore our DS! (error in revs 2.11 and before)
  650.  
  651.                 rcl     bl, 1                   ; Save carry flag in low bit of bl
  652.  
  653.                 mov     dx, offset @code:fname  ; DS:DX -> file name
  654.                 mov     ah, 41h                 ; DOS function 41h, delete file
  655.                 push    bx
  656.                 int     21h                     ; Call DOS
  657.                 pop     bx
  658.  
  659.                 rcr     bl, 1                   ; Restore carry flag from low bit of bl
  660.  
  661.                 ret
  662. restore_disk    endp
  663.  
  664. ; *****************************************************************************
  665. ; rest_disk_seg Attempts to restore a chunk of RAM from the DOS disk file
  666. ;
  667. ; Entry:        ES points to the segment to restore
  668. ;               AX contains its length (in paragraphs)
  669. ;               handle contains the file handle to read from
  670. ;               Program was saved to DOS disk file (fname)
  671. ;
  672. ; Return:       Carry set on error, carry clear on success
  673. ; *****************************************************************************
  674. rest_disk_seg   proc    near
  675.                 push    es
  676.                 push    ds
  677.  
  678.                 mov     bx, es
  679.                 mov     ds, bx                  ; DS -> segment to restore to
  680.  
  681.                 assume  ds:nothing
  682.  
  683.                 mov     wptr cs:paras_left, ax  ; Keep count in this variable
  684.  
  685. disk_read_32k:  cmp     ax, 0800h                   ; Less than 32K left?
  686.                 jb      last_disk_read              ; Yes, do last read
  687.                 sub     wptr cs:paras_left, 0800h   ; 32K left to read
  688.                 mov     ah, 3Fh                 ; DOS function 3Fh, read file
  689.                 mov     bx, wptr cs:handle      ; BX = handle to read from
  690.                 mov     cx, 8000h               ; Read 32K bytes
  691.                 xor     dx, dx                  ; DS:DX -> buffer to read to
  692.                 int     21h                     ; Call DOS
  693.                 jc      rest_disk_seg_er        ; Carry set = error
  694.  
  695. disk_read_ok:   mov     ax, ds                  ; Address next read location
  696.                 add     ax, 0800h               ; It's 800h paragraphs ahead
  697.                 mov     ds, ax                  ; DS -> new restore location
  698.                 mov     ax, wptr cs:paras_left  ; Expecting this above
  699.                 jmp     short disk_read_32k     ; Read next 32K
  700.  
  701. last_disk_read: mov     cx, 4                   ; Convert paragraphs to bytes
  702.                 shl     ax, cl
  703.                 mov     cx, ax                  ; # of bytes left in cx
  704.                 mov     ah, 3Fh                 ; Read last bytes
  705.                 mov     bx, wptr cs:handle      ; BX = handle to read from
  706.                 xor     dx, dx                  ; DS:DX -> buffer to restore to
  707.                 int     21h                     ; Call DOS
  708.                 jc      rest_disk_seg_er        ; Error reading!  Close file first
  709.  
  710. rest_disk_seg_ok:
  711.                 clc
  712.                 jmp     short rest_disk_seg_ret
  713.  
  714. rest_disk_seg_er:
  715.                 stc
  716.  
  717. rest_disk_seg_ret:
  718.                 pop     ds
  719.                 pop     es
  720.                 ret
  721. rest_disk_seg   endp
  722.  
  723. ENDIF
  724. ; *****************************************************************************
  725.  
  726.  
  727.                 
  728. ; *****************************************************************************
  729. ; execute_program   Execute the program specified
  730. ;
  731. ; Entry:            param_blk has been initialized
  732. ;                   DS points to our data
  733. ; Return:           puts return code in cs:exec_ret
  734. ; *****************************************************************************
  735. execute_program proc    near                    ; Called only from inside our segment
  736.  
  737.                 push    ds                      ; These are destroyed by the
  738.                 push    es                      ;  DOS EXEC call
  739.  
  740.                 assume  ds:@code                ; Tell MASM that DS points to our variables
  741.  
  742. exec_program:   mov     ax, ds                  ; Our path name is in CS (point DS to our segment)
  743.                 mov     es, ax                  ; Our parameter block is in CS (point ES to our segment)
  744.                 mov     ax, 4B00h               ; Load and execute program
  745.                 mov     bx, offset @code:param_blk
  746.                 mov     dx, offset @code:prog_name
  747.                 int     21h                     ; Sets carry flag if error
  748.                                                 ; All registers destroyed
  749.                                                 ;  except CS:IP!
  750.  
  751.                 assume  ds:nothing              ; Tell MASM that DS doesn't point to our variables
  752.  
  753.                 mov     bptr cs:exec_ret, al    ; Store EXEC code
  754.                 jc      exec_err                ; Ooops
  755.  
  756. get_return:     mov     ah, 4Dh                 ; DOS function to get ret code
  757.                 int     21h                     ; All registers destroyed
  758.                 mov     bptr cs:exec_ret, al    ; Store EXEC code
  759.                 jmp     short exec_exit
  760.  
  761. exec_err:       mov     wptr cs:ret_code, 3     ; Signal error on executing
  762.  
  763. exec_exit:      pop     es
  764.                 pop     ds
  765.  
  766.                 ret
  767.  
  768. execute_program endp
  769.  
  770.  
  771. ; *****************************************************************************
  772. ; err_exit          Prints error message and terminates program
  773. ;
  774. ; Entry:            Nothing.
  775. ; Returns:          Doesn't return--calls DOS terminate function.
  776. ;                   Naturally, we can't use the C runtime routines,
  777. ;                   since they are swapped out.
  778. ; *****************************************************************************
  779. err_exit        proc    near                    ; Called only from inside our segment
  780.  
  781.                 mov     ax, cs
  782.                 mov     ds, ax                  ; Point DS to our data
  783.  
  784.                 assume  ds:@code                ; Tell MASM that DS points to our data
  785.  
  786.                 mov     ah, 40h                 ; DOS function to write to file
  787.                 mov     bx, STDERR              ; Write to standard error handle
  788.                 mov     cx, wptr abort_len      ; CX = length of message
  789.                 mov     dx, offset @code:abort_msg  ; DS:DX = message
  790.                 int     21h
  791.  
  792.                 mov     ax, 4CFFh           ; Exit, return code 255 decimal (FF hex)
  793.                 int     21h                 ; Exit to DOS, no return
  794.  
  795. err_exit        endp
  796.  
  797.  
  798. ; *****************************************************************************
  799. ; do_exec           Calls the execute routine, then restores program
  800. ;
  801. ; Entry:            Nothing
  802. ; Returns:          Since it is called from the non-resident area, it
  803. ;                   can only return if the program is restored completely.
  804. ; *****************************************************************************
  805. do_exec         proc
  806.                 call    near ptr execute_program    ; Execute the specified program
  807.                 jnc     re_size                     ; No carry, OK
  808.  
  809. exec_er:        mov     wptr ret_code, 3        ; Signal error
  810.  
  811. re_size:        mov     es, wptr my_psp         ; Get our PSP address
  812.                 mov     bx, wptr old_size       ; Increase back to old size
  813.                 mov     ah, 4Ah                 ; DOS function 4Ah = resize
  814.                 int     21h
  815.                 jc      resize_err              ; Carry clear = all OK
  816.  
  817. IFNDEF NOFRAG
  818. ; If necessary, allocate all extra DOS memory blocks our program owned
  819.  
  820.                 mov     cx, wptr extra_count    ; CX = number of extra DOS blocks
  821.                 jcxz    restore_prog            ; If zero, don't bother
  822.                 mov     di, offset dos_blocks   ; DI -> array of addresses/sizes
  823.  
  824.                 push    es
  825.  
  826. alloc_extra_loop:
  827.                 mov     bx, wptr [di].block_size; BX = old size
  828.                 mov     ah, 48h                 ; DOS function to allocate memory block
  829.                 push    cx
  830.                 push    di
  831.                 int     21h
  832.                 pop     di
  833.                 pop     cx
  834.                 jc      resize_err              ; Unlikely error
  835.  
  836. check_alloc:    cmp     ax, wptr [di].block_seg ; Is it the same as the origignal segment address?
  837.                 jnz     resize_err              ; Nope.  We could do some fancy tricks here,
  838.                                                 ;  but for the most part it's not necessary.
  839.  
  840.                 add     di, size dos_block      ; Point to next entry
  841.                 loop    alloc_extra_loop        ; Keep going through extra blocks
  842.  
  843.                 pop     es
  844. ENDIF
  845.                 jmp     short restore_prog
  846.  
  847. resize_err:     call    near ptr err_exit       ; Can't return, exit to DOS
  848.  
  849. restore_prog:   call    wptr restore_proc       ; Restore program from disk
  850.                 jc      resize_err              ; Carry set if error
  851.                                                 ; If no error, it returns
  852.                                                 ;  down to restored code
  853.                 ret
  854. do_exec         endp
  855.  
  856. ; *****************************************************************************
  857. ; *****************************************************************************
  858. ALIGN 10h       ; Aligns next code item on paragraph boundary
  859.                 ; para_align is a proc instead of just a data
  860.                 ;  item because the ALIGN directive in MASM only
  861.                 ;  applies to code items, not data items!
  862. para_align      proc    near
  863. new_mcb         db      16 dup (0)          ; DOS will put MCB of released memory here
  864. para_align      endp
  865. ; *****************************************************************************
  866. ; *****************************************************************************
  867.  
  868. ; *****************************************************************************
  869. ; Everything after here is only needed BEFORE we change our allocation size.
  870. ;  Everything below this line will be (temporarily) swapped out of memory,
  871. ;  and thus cannot be used once we shrink our memory allocation.
  872. ; *****************************************************************************
  873.  
  874. ; *****************************************************************************
  875. ;   swap        The routine that does it all
  876. ;
  877. ;   Callable by a C program, takes these parameters (regardless
  878. ;     of which swap options chosen at assembly time, because
  879. ;     C calling conventions let us ignore parameters to the
  880. ;     right if we want to):
  881. ;
  882. ;   swap_both:
  883. ;       prog        Full path name of program to execute
  884. ;       cmdline     Command-line parameters for program to execute
  885. ;       return      Pointer to byte for return code of exec'd program
  886. ;       save_file   Full path name of file in which to save program image (if disk is to be used)
  887. ;
  888. ;   Depending on the memory model used, the pointers to the
  889. ;   parameters each occupy 2 bytes or 4 bytes on the stack.
  890. ;   If there is only one data segment (Small and Medium), each
  891. ;   value is a 2-byte near pointer, with DS assumed as the segment
  892. ;   register.  If there are multiple data segments (Compact and
  893. ;   Large), each value is a 4-byte far pointer, with segment and
  894. ;   offset values each pushed on the stack.
  895. ;
  896. ;   The function is declared with 4 parameters, regardless of whether
  897. ;   disk swapping is being included.  This is because the file name
  898. ;   parameter is the last on the parameter list, which C lets us
  899. ;   ignore if we want.
  900. ;
  901. ;   The swap() routine does not check the program name or command
  902. ;   line to verify that a legal command has been requested--that's
  903. ;   the caller's responsibility!
  904. ;
  905. ; *****************************************************************************
  906.  
  907.                 public  swap
  908.  
  909. swap            proc    prog:PTR, cmdline:PTR, return:PTR, save_file:PTR
  910.  
  911.                 push    si                      ; Save registers needed
  912.                 push    di                      ;  by the caller
  913.                 push    es
  914.                 push    ds
  915.  
  916. point_segs:     mov     ax, cs                  ; Point ES to our segment
  917.                 mov     es, ax                  ;  for copying of parameters
  918.  
  919. ; *****************************************************************************
  920. get_name:       ; Copy program name to our variable, all versions
  921.  
  922. ; If multiple data segments, load DS:SI from stack.  Else, just load SI
  923. IF @datasize
  924.                 push    ds                      ; Save segment register
  925.                 lds     si, dptr prog           ; Load 32-bit far pointer
  926. ELSE
  927.                 mov     si, wptr prog           ; Load 16-bit near pointer
  928. ENDIF                                           ; DS:SI -> program name from caller
  929.  
  930.                 mov     di, offset @code:prog_name  ; ES:DI -> our storage area
  931.  
  932. name_loop:      lodsb                           ; Fetch next byte
  933.                 stosb                           ; Save next byte
  934.                 or      al, al                  ; Was it 0 (end of string)?
  935.                 jnz     name_loop               ; No, get next one
  936.  
  937. IF @datasize
  938.                 pop     ds                      ; Pop DS if it was pushed above
  939. ENDIF
  940. ; *****************************************************************************
  941.  
  942. ; *****************************************************************************
  943. get_cmd:        ; Copy command line to our variable, all versions
  944.  
  945. ; If multiple data segments, load DS:SI from stack.  Else, just load SI
  946. IF @datasize
  947.                 push    ds                      ; Save segment register
  948.                 lds     si, dptr cmdline        ; Load 32-bit far pointer
  949. ELSE
  950.                 mov     si, wptr cmdline        ; Load 16-bit near pointer
  951. ENDIF                                           ; DS:SI -> command line from caller
  952.                 
  953.                 mov     di, offset @code:cmd_line   ; ES:DI -> our storage area
  954.                 xor     cl, cl                  ; Keep track of length in cl
  955.  
  956. cmd_loop:       lodsb                           ; Fetch next byte from DS:SI
  957.                 or      al, al                  ; Was it 0 (end of string)?
  958.                 jz      cmd_end                 ; Yes, we're done
  959.                 stosb                           ; No, store byte
  960.                 inc     cl                      ; Increment length
  961.                 cmp     cl, MAX_DOS_CMD         ; Are we at maximum cmd length?
  962.                 jnz     cmd_loop                ; Nope, keep going
  963.  
  964. cmd_end:        mov     bptr es:[di], 0dh       ; Put CR at end of cmd line
  965.                 mov     bptr cs:cmd_len, cl     ; Store command-line length
  966.  
  967. IF @datasize
  968.                 pop     ds                      ; Pop DS if it was pushed above
  969. ENDIF
  970. ; *****************************************************************************
  971. ; Set up the default FCBs at 5Ch and 6Ch in the PSP
  972. ;  Code provided by David E. Jenkins
  973.                 push    ds                      ; Save caller's DS
  974.  
  975.                 mov     ax, cs                  ; Point DS to our
  976.                 mov     ds, ax                  ;  variables
  977.  
  978.                 assume  ds:@code                ; Tell MASM that DS points to our variables
  979. ;
  980. ;   Locate the first two command line arguments
  981. ;
  982.                 push    ds                      ; Copy ds into es
  983.                 pop     es                      ;  "   "   "   "
  984.                 mov     di, offset @code:cmd_line   ; Point to command line in CS
  985.                 mov     al, bptr cmd_len            ; load the command line length
  986.                 xor     ah, ah
  987.                 inc     ax                      ; Include the CR in the length
  988.                 mov     wptr c_l_length, ax     ; Save the command line length
  989.                 add     ax, di                  ; Point to end of command line
  990.                 mov     wptr si_5c, ax          ; default to just after command line
  991.                 mov     wptr si_6c, ax          ;    "    "   "     "      "     "
  992.                 cmp     bptr cmd_len, 0         ; Is there anything to parse?
  993.                 jz      args_located            ; if not then args have been located
  994.  
  995.                 mov     cx, wptr c_l_length     ; Load the command line length
  996.                 mov     al, ' '                 ; We must find the first non-blank
  997.                 repe    scasb                   ; Go until we find it or run out
  998.                 or      cx, cx                  ; Did we run out (CX = 0)?
  999.                 jz      args_located            ; Yes--then args have been located
  1000.  
  1001.                 dec     di                      ; Move back to the right one
  1002.                 inc     cx                      ;  "    "   "   "    "    "
  1003.                 mov     wptr si_5c, di          ; Save the location of arg 1
  1004.                 repne   scasb                   ; Find the next space (between arg1,2)
  1005.                 or      cx, cx                  ; Did we run out
  1006.                 jz      args_located            ; If so then args have been located
  1007.  
  1008.                 dec     di                      ; Move back to the left one
  1009.                 inc     cx                      ;  "    "   "   "    "   "
  1010.                 repe    scasb                   ; Now find next non-blank (arg 2)
  1011.                 or      cx, cx                  ; Did we run out
  1012.                 jz      args_located            ; If so then args have been located
  1013.  
  1014.                 dec     di                      ; Move back to the right one
  1015.                 inc     cx                      ;  "    "   "   "    "    "
  1016.                 mov     wptr si_6c,di           ; Save location of arg 2
  1017.  
  1018. args_located:
  1019. ; parse the first argument into the first FCB
  1020.  
  1021.                 mov     si, wptr si_5c                  ; Point to the first argument
  1022.                 mov     di, offset @code:fcb5C_drive    ; Point to the unopened FCB
  1023.                 mov     ah, 29h                 ; Parse file name function
  1024.                 mov     al, 00h                 ; Do it like COMMAND.COM does it
  1025.                 int     21h                     ; go for it
  1026.  
  1027. ; parse the second argument into the second FCB
  1028.                 mov     si, wptr si_6c                  ; Point to the second argument
  1029.                 mov     di, offset @code:fcb6c_drive    ; point to the unopened FCB
  1030.                 mov     ah, 29h                 ; Parse file name function
  1031.                 mov     al, 00h                 ; Do it like COMMAND.COM does it
  1032.                 int     21h                     ; go for it
  1033.  
  1034.                 pop     ds                      ; Restore caller's DS
  1035.  
  1036. ; *****************************************************************************
  1037. ; Get the file name from the command line, if this version needs it
  1038. IFDEF USE_DISK
  1039. get_file:
  1040.  
  1041. ; If multiple data segments, load DS:SI, else just load SI
  1042. IF @datasize
  1043.                 push    ds                      ; Save segment register
  1044.                 lds     si, dptr save_file      ; Load 32-bit pointer
  1045. ELSE
  1046.                 mov     si, save_file           ; Load 16-bit pointer
  1047. ENDIF                                           ; DS:SI -> swap file name from caller
  1048.  
  1049.                 mov     di, offset @code:fname  ; ES:DI -> our storage area
  1050.  
  1051. resolve:        mov     ah, 60h                 ; DOS INTERNAL function to resolve file name to full path name
  1052.                 int     21h                     ; Stores complete path at ES:DI--we need it after EXEC in case
  1053.                                                 ;  current drive or directory have changed
  1054.                                                 ; Ignore file name error here--it
  1055.                                                 ;  will be caught in save_disk if need be
  1056.  
  1057. IF @datasize
  1058.                 pop     ds                      ; Pop DS if it was pushed above
  1059. ENDIF
  1060.  
  1061. ENDIF           ; IFDEF disk
  1062. ; *****************************************************************************
  1063. ; We have the parameters--let's go
  1064. ; *****************************************************************************
  1065.  
  1066.                 mov     wptr cs:ret_code, 0     ; Initialize swap's return code
  1067.                 mov     cs:exec_ret, 0          ; Initialize exec's return code
  1068.  
  1069. save_stack:     mov     ax, ss
  1070.                 mov     wptr cs:old_ss, ax      ; Save current SS
  1071.                 mov     ax, sp
  1072.                 mov     wptr cs:old_sp, ax      ; Save current SP
  1073.  
  1074. our_stack:      mov     ax, cs                  ; Our stack is in our CS
  1075.                 cli                             ; Disable interrupts
  1076.                 mov     ss, ax
  1077.                 mov     sp, offset @code:new_sp ; Set new stack
  1078.                 sti                             ; Re-enable interrupts
  1079.  
  1080. save_regs:      push    es                      ; Save needed registers
  1081.                 push    ds                      ; This is the caller's DS!
  1082.                 push    bp
  1083.  
  1084.                 mov     ax, cs
  1085.                 mov     ds, ax                  ; Point DS to our data
  1086.  
  1087.                 assume  ds:@code                ; Tell MASM that DS points to our variables
  1088.  
  1089. save_info:      mov     ah, 51h                 ; DOS function 51h, get PSP
  1090.                 int     21h                     ; Call DOS
  1091.                 mov     ax, bx                  ; ax = PSP
  1092.                 mov     wptr my_psp, ax         ; Save in cs: addressable location
  1093.                 dec     ax                      ; PSP-1 = MCB for this mem block
  1094.                 mov     es, ax
  1095.                 mov     ax, es:[0001h]          ; Get PSP address--should be same!
  1096.                 cmp     ax, wptr my_psp         ; All kosher?
  1097.                 jz      psp_ok                  ; Yes
  1098.  
  1099. psp_error:      mov     wptr ret_code, 1        ; No, pass return code
  1100.                 jmp     short exit_swap         ; Exit
  1101.  
  1102. psp_ok:         call    near ptr calc_size      ; Calc size to keep, save
  1103.  
  1104. try_save:       call    near ptr save_program   ; Write program to disk
  1105.                 jnc     shrink_mem              ; Carry flag set on error
  1106.  
  1107. no_save:        mov     wptr ret_code, 2        ; Error--set return code
  1108.                 jmp     short exit_swap         ; Exit routine on error
  1109.  
  1110. shrink_mem:     mov     ah, 4Ah                 ; DOS 4Ah--modify memory allocation
  1111.                 mov     es, wptr my_psp         ; Point to PSP again
  1112.                 mov     bx, wptr new_size       ; new_size was figured in calc_size
  1113.                 int     21h                     ; Call DOS to shrink size
  1114.                 jc      no_shrink               ; Carry set = error
  1115.  
  1116. IFNDEF NOFRAG
  1117. ; If necessary, free all extra DOS memory blocks our program owns
  1118.  
  1119.                 mov     cx, wptr extra_count    ; CX = number of extra DOS blocks
  1120.                 jcxz    exec_prog               ; If zero, don't bother
  1121.                 mov     di, offset dos_blocks   ; DI -> array of addresses/sizes
  1122.  
  1123.                 push    es
  1124.  
  1125. free_extra_loop:
  1126.                 mov     ax, wptr [di].block_seg
  1127.                 mov     es, ax                  ; ES = DOS memory segment to free
  1128.                 mov     ah, 49h                 ; DOS function to free memory block
  1129.                 push    cx
  1130.                 push    di
  1131.                 int     21h
  1132.                 pop     di
  1133.                 pop     cx
  1134.                 jc      no_shrink               ; Unlikely error
  1135.                 add     di, size dos_block      ; Point to next entry
  1136.                 loop    free_extra_loop         ; Keep going through extra blocks
  1137.  
  1138.                 pop     es
  1139. ENDIF
  1140.  
  1141.                 jmp     short exec_prog
  1142.  
  1143. ; *****************************************************************************
  1144. ; Any routine called or data referred to after this point MUST be located
  1145. ;  in this source file BEFORE the variable new_mcb below!
  1146. ; *****************************************************************************
  1147.  
  1148. no_shrink:      mov     wptr ret_code, 1        ; Carry = couldn't shrink block
  1149.                 jmp     short exit_swap         ; Should delete file here!
  1150.  
  1151. exec_prog:      call    do_exec                 ; This code is resident, and can
  1152.                                                 ;  be found above the resident line
  1153.  
  1154. ; do_exec execute the routine AND restores the program!
  1155.  
  1156. exit_swap:      pop     bp                      ; Restore saved registers
  1157.                 pop     ds                      ; This is the caller's DS!
  1158.                 pop     es
  1159.  
  1160.                 assume  ds:nothing              ; Tell MASM DS doesn't point to our variables
  1161.  
  1162. prev_stack:     mov     ax, wptr cs:old_ss      ; Restore original stack
  1163.                 cli
  1164.                 mov     ss, ax
  1165.                 mov     sp, wptr cs:old_sp
  1166.                 sti
  1167.  
  1168. ; Giving user exec's return code.  It could be a 16- or 32-bit pointer
  1169. IF @datasize
  1170.                 push    ds
  1171.                 lds     si, dptr return         ; Load 32-bit pointer
  1172. ELSE
  1173.                 mov     si, wptr return         ; Load 16-bit pointer
  1174. ENDIF                                           ; DS:SI -> return code variable
  1175.                 
  1176.                 mov     al, bptr cs:exec_ret    ; Store exec's return code
  1177.                 mov     bptr [si], al           ;  at address specified by caller
  1178.  
  1179. IF @datasize
  1180.                 pop     ds                      ; Pop DS if pushed above
  1181. ENDIF
  1182.  
  1183.                 pop     ds
  1184.                 pop     es
  1185.                 pop     di
  1186.                 pop     si
  1187.                 mov     ax, wptr cs:ret_code    ; Give return code
  1188.                 ret
  1189. swap            endp
  1190.  
  1191. ; *****************************************************************************
  1192. ; *****************************************************************************
  1193. ; calc_size     Calculates the total size (in paragraphs) of all DOS blocks
  1194. ;               owned by this program plus the amount of the initial program
  1195. ;               allocation block we can swap out.
  1196. ;
  1197. ; Entry:        DS points to our variables
  1198. ;               ES points to DOS Memory Control Block for our program
  1199. ;
  1200. ; Return:       old_size, start_seg, new_size, total_paras, extra_count initialized
  1201. ; *****************************************************************************
  1202. calc_size       proc    near                    ; Called only from inside our segment
  1203.  
  1204.                 push    es
  1205.  
  1206.                 assume  ds:@code                ; Tell MASM that DS points to our variables
  1207.  
  1208.                 mov     ax, es:[0003h]          ; Get # paragraphs allocated
  1209.                                                 ;  in this memory block
  1210.                 mov     wptr old_size, ax       ; Save old size of program
  1211.                 mov     bx, cs                  ; BX = segment of our code
  1212.                 mov     ax, offset @code:new_mcb; Last address to keep
  1213.                 mov     cl, 4                   ; new_mcb is para aligned
  1214.                 shr     ax, cl                  ; AX = ofs new_mcb / 16
  1215.                 inc     ax
  1216.                 add     bx, ax
  1217.                 mov     wptr start_seg, bx      ; Segment of released memory
  1218.                 sub     bx, wptr my_psp         ; BX = size to keep in paragraphs
  1219.                 mov     wptr new_size, bx       ; Save new, smaller size
  1220.                 mov     ax, wptr old_size
  1221.                 sub     ax, bx
  1222.                 mov     wptr prog_size, ax      ; ax = size of program block to swap out
  1223.                 mov     wptr total_paras, ax    ; ax = total paragraphs
  1224.  
  1225. IFNDEF NOFRAG
  1226. ; Now loop through all subsequent MCBs looking for blocks that we own (if
  1227. ;  the MCB's "owner" (PSP) matches us (our PSP).  Right now ES points to
  1228. ;  our MCB.  The MCB has three fields of interest:
  1229. ;
  1230. ;   Offset  Size    Description
  1231. ;   -------------------------------------------------------------------------
  1232. ;   0000h   Byte    Chain flag: 'M' (4Dh) if not last, 'Z' (5Ah) if last block in chain
  1233. ;   0001h   Word    PSP segment of owner, 0000h if free memory
  1234. ;   0003h   Word    Size of memory block in paragraphs, NOT including this MCB!
  1235.  
  1236. find_extras:    mov     wptr extra_count, 0     ; Initialize count
  1237.                 mov     bx, wptr my_psp         ; Use bx to hold PSP for easy comparisons
  1238.                 mov     di, offset dos_blocks   ; di = pointer to storage area
  1239.  
  1240. check_next_mcb: cmp     bptr es:[0000h], 'Z'    ; Is this the last block?
  1241.                 jz      calc_size_ret           ; Yup
  1242.  
  1243. next_mcb2:      mov     ax, es                  ; ax = this MCB
  1244.                 mov     cx, wptr es:[0003h]     ; cx = size of this mcb
  1245.                 add     ax, cx
  1246.                 inc     ax                      ; ax = addres of next MCB
  1247.                 mov     es, ax                  ; ES -> next MCB
  1248.  
  1249. my_block:       cmp     wptr es:[0001h], bx     ; Does it match my PSP?
  1250.                 jnz     check_next_mcb          ; Nope, move along
  1251.  
  1252. is_my_block:    inc     wptr extra_count        ; One more extra block
  1253.                 cmp     wptr extra_count, MAX_EXTRA
  1254.                 ja      calc_size_ret           ; Too many blocks--just exit
  1255.  
  1256. is_my_block2:   inc     ax                      ; Was MCB, now is address of segment
  1257.                 mov     wptr [di].block_seg, ax ; Store segment address
  1258.                 mov     cx, wptr es:[0003h]     ; Get size in paragraphs
  1259.                 mov     wptr [di].block_size, cx; Store size
  1260.                 add     wptr total_paras, cx    ; Increment total
  1261.                 add     di, size dos_block      ; Next index (move pointer)
  1262.                 jmp     short check_next_mcb
  1263. ENDIF
  1264.  
  1265. calc_size_ret:  pop     es
  1266.                 ret
  1267.  
  1268. calc_size       endp
  1269. ; *****************************************************************************
  1270.  
  1271. ; *****************************************************************************
  1272. ; xms_installed     Checks to see if XMS driver (himem.sys) is loaded
  1273. ;
  1274. ; Entry:            No assumptions--can be called by user
  1275. ; Return:           1 if XMS driver is load, 0 if not
  1276. ; *****************************************************************************
  1277. IFDEF USE_XMS
  1278.                 public  xms_installed
  1279. xms_installed   proc                            ; Called by user also!
  1280.  
  1281.                 push    ds                  ; Save all "important" registers
  1282.                 push    si
  1283.                 push    es
  1284.                 push    di
  1285.  
  1286.                 mov     ax, 4300h           ; Multiplex code for XMS driver, load check function
  1287.                 int     2Fh                 ; Call multiplex interrupt
  1288.                 cmp     al, 80h             ; al = 80h means XMS driver IS loaded
  1289.                 jnz     no_xms              ; Nope, not there
  1290.  
  1291. yes_xms:        mov     ax, 4310h               ; Get address of entry point
  1292.                 int     2Fh                     ; Returns address in ES:BX
  1293.                 mov     wptr cs:XMS_proc, bx
  1294.                 mov     wptr cs:XMS_proc + 2, es
  1295.                 mov     ax, 1                   ; Return 1, XMS installed
  1296.                 jmp     short xms_ret
  1297.  
  1298. no_xms:         xor     ax, ax              ; Return 0, XMS not installed
  1299.  
  1300. xms_ret:        pop     di
  1301.                 pop     es
  1302.                 pop     si
  1303.                 pop     ds
  1304.                 ret
  1305.  
  1306. xms_installed   endp
  1307. ENDIF
  1308. ; *****************************************************************************
  1309.  
  1310. ; *****************************************************************************
  1311. ; ems4_installed    Checks to see if EMS 4.0 or above driver is loaded
  1312. ;
  1313. ; Entry:            No assumptions--can be called by user
  1314. ; Return:           1 if EMS 4.0 driver is load, 0 if not
  1315. ; *****************************************************************************
  1316. IFDEF USE_EMS
  1317.                 public  ems4_installed
  1318. ems4_installed  proc                            ; Called by user also!
  1319.  
  1320.                 push    ds                      ; Save "important" registers
  1321.                 push    si
  1322.                 push    es
  1323.                 push    di
  1324.  
  1325.  
  1326. get_emm_vector: mov     ah, GET_VECTOR          ; Get EMM interrupt vector
  1327.                 mov     al, 67h                 ; EMM accessed through Int 67h
  1328.                 int     21h                     ; Call DOS to get vector
  1329.                 mov     di, 0ah                 ; vector + di = name
  1330.                 mov     ax, cs
  1331.                 mov     ds, ax                  ; DS:SI -> EMM device driver name
  1332.                 mov     si, offset @code:emm_name   ; Compare with EMM device name
  1333.                 mov     cx, EMM_NAME_LEN
  1334.                 cld
  1335.                 repe    cmpsb                   ; Compare bytes
  1336.                 jnz     ems_no                  ; Same?  If not, EMS installed
  1337.  
  1338. ems_yes:        mov     ah, 46h                 ; Get EMM version number
  1339.                 int     67h                     ; Returns BCD in al
  1340.                 cmp     al, 40h                 ; Look only at high 4 bits
  1341.                 jb      ems_no                  ; Version not high enough--return 0
  1342.  
  1343. ems4_yes:       mov     ax, 1                   ; EMS installed, return 1
  1344.                 jmp     short ems_ret
  1345.  
  1346. ems_no:         xor     ax, ax                  ; EMS not installed, return 0
  1347.  
  1348. ems_ret:        pop     di
  1349.                 pop     es
  1350.                 pop     si
  1351.                 pop     ds
  1352.                 ret
  1353.  
  1354. ems4_installed  endp
  1355. ENDIF
  1356. ; *****************************************************************************
  1357.  
  1358.  
  1359. ; *****************************************************************************
  1360. ; save_program      Try to save in XMS/EMS/disk.
  1361. ;
  1362. ; Entry:            DS points to our variables
  1363. ;
  1364. ; Returns:          Success:  carry flag clear
  1365. ;                   Failure:  carry flag set
  1366. ; *****************************************************************************
  1367. save_program    proc    near            ; Called only from inside our segment
  1368.  
  1369.                 push    si              ; Save registers
  1370.                 push    di
  1371.                 push    ds
  1372.                 push    es
  1373.  
  1374. ; Now figure out which routines to call, based on command-line definitions
  1375. ; To change the order in which swap() attempts to swap, change the order
  1376. ;  of these three conditional blocks.
  1377. IF1
  1378.    %out swap() will attempt to save the program in the following order:
  1379. ENDIF
  1380.    
  1381.  
  1382. ; *****************************************************************************
  1383. IFDEF USE_XMS
  1384. IF1
  1385.    %out -- XMS extended memory
  1386. ENDIF
  1387.                 call    save_xms        ; Try saving to XMS extended memory
  1388.                 jnc     save_ok         ; Carry clear == success, all done
  1389. ENDIF
  1390. ; *****************************************************************************
  1391.  
  1392.  
  1393. ; *****************************************************************************
  1394. IFDEF USE_EMS
  1395. IF1
  1396.    %out -- EMS expanded memory
  1397. ENDIF
  1398.                 call    save_ems        ; Try saving to EMS expanded memory
  1399.                 jnc     save_ok       ; Carry clear == success, all done
  1400. ENDIF
  1401. ; *****************************************************************************
  1402.  
  1403.  
  1404. ; *****************************************************************************
  1405. IFDEF USE_DISK
  1406. IF1
  1407.    %out -- DOS disk file
  1408. ENDIF
  1409.                 call    save_disk       ; Try saving to DOS disk file
  1410.                 jnc     save_ok         ; Carry clear == success, all done
  1411. ENDIF
  1412. ; *****************************************************************************
  1413.  
  1414. save_er:        stc                     ; Couldn't save anywhere, return error
  1415.                 jmp     short save_ret
  1416.  
  1417. save_ok:        clc                     ; Saved successfully, return OK
  1418.  
  1419. save_ret:       pop     es              ; Restore registers
  1420.                 pop     ds
  1421.                 pop     di
  1422.                 pop     si
  1423.  
  1424.                 ret
  1425. save_program    endp
  1426. ; *****************************************************************************
  1427.  
  1428.  
  1429. ; *****************************************************************************
  1430. ; Version-dependent code--only assemble the routine to save the program
  1431. ; to each place if it was requested on the command line
  1432. ; *****************************************************************************
  1433.  
  1434.  
  1435. ; *****************************************************************************
  1436. ; save_xms      Attempts to save program to XMS extended memory
  1437. ;
  1438. ; Entry:        DS points to our variables
  1439. ;
  1440. ; Return:       Carry set on error, carry clear on success
  1441. ;               If successful, updates restore_proc with the address of
  1442. ;               the XMS restore routine
  1443. ; *****************************************************************************
  1444. IFDEF USE_XMS
  1445. save_xms        proc    near
  1446.  
  1447.                 assume  ds:@code                ; Tell MASM DS points to our variables
  1448.  
  1449.                 call    xms_installed           ; Check if XMS installed
  1450.                 or      ax, ax                  ; Returns 0 if not installed
  1451.                 jnz     xms_inst                ; AX != 0, XMS installed
  1452.                 jmp     short save_xms_er       ; AX == 0, XMS not installed
  1453.  
  1454. xms_inst:       mov     dx, wptr total_paras    ; dx = total # of paragraphs to write
  1455.                 mov     cl, 6                   ; Convert Paragraphs to kilobytes
  1456.                 shr     dx, cl                  ; dx = dx / 64
  1457.                 inc     dx                      ; dx = kilobytes needed (plus 1 for safety)
  1458.  
  1459. xms_alloc:      mov     ah, 09h                 ; XMS function 09, allocate extended memory block
  1460.                 call    dptr XMS_proc           ; Call XMS entry point directly
  1461.                 cmp     ax, 1                   ; AX = 1 on success
  1462.                 jnz     save_xms_er             ; Allocation unsuccessful, error
  1463.  
  1464. xms_alloc_ok:   mov     wptr handle, dx         ; Save returned handle in DX
  1465.  
  1466. ; First, attempt to save the portion of the program block
  1467. xms_prog_save:  mov     ax, wptr start_seg      ; Released segment address
  1468.                 mov     es, ax
  1469.                 mov     ax, wptr prog_size      ; Size (in paragraphs) of program block to save
  1470.                 xor     bx, bx
  1471.                 mov     wptr XMS_to_addr, bx    ; Initialize XMS destination
  1472.                 mov     wptr XMS_to_addr + 2, bx;  address (offset into extended memory block)
  1473.  
  1474.                 call    save_xms_seg            ; Attempt to save the program block
  1475.                 jc      write_error             ; Carry set = failure, return
  1476.  
  1477. IFNDEF NOFRAG
  1478. ; Next, save the extra DOS segments
  1479. xms_extra_save: mov     cx, wptr extra_count    ; Number of extra blocks to save
  1480.                 jcxz    save_xms_ok             ; If CX = 0, we exit routine
  1481.  
  1482.                 mov     di, offset dos_blocks   ; DI -> array of segment/size pairs
  1483.  
  1484. xms_extra_save_loop:
  1485.                 mov     ax, wptr [di].block_seg
  1486.                 mov     es, ax                  ; ES = segment to save
  1487.                 mov     ax, wptr [di].block_size; AX = size in paragraphs
  1488.                 push    cx
  1489.                 push    di
  1490.                 call    save_xms_seg            ; Attempt to save this block
  1491.                 pop     di
  1492.                 pop     cx
  1493.                 jc      write_error             ; Carry flag set == error
  1494.                 add     di, size dos_block
  1495.                 loop    xms_extra_save_loop     ; Keep going through all blocks
  1496.  
  1497. ENDIF
  1498.                 jmp     short save_xms_ok
  1499.  
  1500. write_error:    mov     dx, wptr handle             ; Free allocated handle
  1501.                 mov     ah, 0Ah
  1502.                 call    dptr XMS_proc               ; Falls through to failure code
  1503.  
  1504. save_xms_er:    stc
  1505.                 jmp     short save_xms_ret
  1506.  
  1507. save_xms_ok:    mov     wptr restore_proc, offset @code:restore_xms     ; Initialize pointer
  1508.                 clc                                                     ;  to restore routine
  1509.  
  1510. save_xms_ret:   ret
  1511. save_xms        endp
  1512.  
  1513.  
  1514. ; *****************************************************************************
  1515. ; save_xms_seg  Attempts to save a chunk of RAM to XMS memory
  1516. ;
  1517. ; Entry:        ES points to the segment to save
  1518. ;               AX contains its length (in paragraphs)
  1519. ;               handle holds the XMS handle to write to
  1520. ;               XMS_to_addr contains offset into extended memory for write
  1521. ;
  1522. ; Return:       Carry set on error, carry clear on success
  1523. ;               Updates XMS_to_addr for next write
  1524. ; *****************************************************************************
  1525. save_xms_seg    proc    near
  1526.                 push    ds
  1527.                 push    es
  1528.  
  1529. ; Call the XMS copy memory function to do this; fill in the XMS request block
  1530. xms_write_size: mov     bx, 10h                     ; AX = # of paragraphs
  1531.                 mul     bx                          ; DX:AX = AX * 10h, convert paragraphs to bytes
  1532.                 mov     wptr XMS_size, ax           ; Store # of bytes to write
  1533.                 mov     wptr XMS_size + 2, dx
  1534.  
  1535. xms_write_from: xor     bx, bx
  1536.                 mov     wptr XMS_from, bx           ; 0 means from conventional memory
  1537.                 mov     wptr XMS_from_addr, bx      ; Offset of source address is 0
  1538.                 mov     ax, es                      ; Segment of source address is ES
  1539.                 mov     wptr XMS_from_addr + 2, ax
  1540.  
  1541. xms_write_to:   mov     ax, wptr handle             ; Destination XMS handle
  1542.                 mov     wptr XMS_to, ax             ;  XMS_to_addr already filled in
  1543.  
  1544. do_xms_write:   mov     si, offset @code:XMS_struc  ; DS:SI -> XMS request structure
  1545.                 mov     ah, 0Bh                     ; Function B, copy memory
  1546.                 call    dptr XMS_proc               ; Do the memory copy move
  1547.                 cmp     ax, 1                       ; AX = 1 means success
  1548.                 jnz     save_xms_seg_er             ; Success, all done!
  1549.  
  1550. save_xms_seg_ok:mov     ax, wptr XMS_size           ; Retrieve length
  1551.                 mov     dx, wptr XMS_size + 2       ;  (32 bits)
  1552.                 add     wptr XMS_to_addr, ax        ; Add two 32-bit values
  1553.                 adc     wptr XMS_to_addr + 2, dx    ; Update XMS write pointer
  1554.                 clc                                 ; Signal success
  1555.                 jmp     short save_xms_seg_ret
  1556.  
  1557. save_xms_seg_er:stc
  1558.  
  1559. save_xms_seg_ret:
  1560.                 pop     es
  1561.                 pop     ds
  1562.                 ret
  1563. save_xms_seg    endp
  1564.  
  1565. ENDIF
  1566. ; *****************************************************************************
  1567.  
  1568.  
  1569. ; *****************************************************************************
  1570. ; save_ems      Attempts to save program to EMS 4.0 expanded memory
  1571. ;
  1572. ; Entry:        DS points to our variables
  1573. ;
  1574. ; Return:       Carry set on error, carry clear on success
  1575. ;               If successful, updates restore_proc with the address of
  1576. ;               the EMS restore routine
  1577. ; *****************************************************************************
  1578. IFDEF USE_EMS
  1579. save_ems        proc    near
  1580.  
  1581.                 assume  ds:@code                ; Tell MASM DS points to our variables
  1582.  
  1583.                 call    ems4_installed          ; Check if EMS 4.0 installed
  1584.                 or      ax, ax                  ; AX = 0 if not installed
  1585.                 jnz     ems_inst                ; AX != 0, ems installed
  1586.                 jmp     short save_ems_er       ; AX = 0, no EMS, error!
  1587.  
  1588. ems_inst:       mov     bx, wptr total_paras    ; Total # of paragraphs we need
  1589.                 mov     cl, 10                  ; Convert Paragraphs to 16K pages
  1590.                 shr     bx, cl
  1591.                 inc     bx                      ; BX = pages needed
  1592.                 mov     bptr pages_used, bl     ; Save for later use
  1593.  
  1594.                 mov     ah, 43h                 ; EMM function 43h, allocate
  1595.                 int     67h
  1596.                 or      ah, ah                  ; OK return code?
  1597.                 jz      ems_alloc_ok            ; Yes, skip ahead
  1598.                 jmp     short save_ems_er       ; No, not enough EMS
  1599.  
  1600. ems_alloc_ok:   mov     wptr handle, dx         ; Returned handle in DX
  1601.  
  1602. ; First, attempt to save the portion of the program block
  1603. ems_prog_save:  mov     ax, wptr start_seg      ; Released segment address
  1604.                 mov     es, ax
  1605.                 mov     ax, wptr prog_size      ; Size (in paragraphs) of program block to save
  1606.  
  1607.                 xor     bx, bx
  1608.                 mov     wptr ems_offset, bx     ; Maintain absolute byte offset
  1609.                 mov     wptr ems_offset + 2, bx ;  pointer into handle
  1610.  
  1611.                 call    save_ems_seg            ; Attempt to save the program block
  1612.  
  1613.                 jc      save_ems_fail           ; Carry set = failure, return
  1614.  
  1615. IFNDEF NOFRAG
  1616. ; Next, save the extra DOS segments
  1617. ems_extra_save: mov     cx, wptr extra_count    ; Number of extra blocks to save
  1618.                 jcxz    save_ems_ok             ; If CX = 0, we exit routine
  1619.  
  1620.                 mov     di, offset dos_blocks   ; DI -> array of segment/size pairs
  1621.  
  1622. ems_extra_save_loop:
  1623.                 mov     ax, wptr [di].block_seg
  1624.                 mov     es, ax                  ; ES = segment to save
  1625.                 mov     ax, wptr [di].block_size; AX = size in paragraphs
  1626.                 push    cx
  1627.                 push    di
  1628.                 call    save_ems_seg            ; Attempt to save this block
  1629.                 pop     di
  1630.                 pop     cx
  1631.                 jc      save_ems_fail           ; Carry flag set == error
  1632.                 add     di, size dos_block
  1633.                 loop    ems_extra_save_loop     ; Keep going through all blocks
  1634. ENDIF
  1635.                 jmp     short save_ems_ok
  1636.  
  1637. save_ems_fail:  mov     dx, wptr handle         ; Failure--free handle
  1638.                 mov     ah, 45h
  1639.                 int     67h                     ; Falls through to failure code
  1640.  
  1641. save_ems_ok:    mov     wptr restore_proc, offset @code:restore_ems     ; Initialize pointer
  1642.                 clc                                                     ;  to restore routine
  1643.                 jmp     short save_ems_ret
  1644.  
  1645. save_ems_er:    stc
  1646.  
  1647. save_ems_ret:   ret
  1648. save_ems        endp
  1649.  
  1650. ; *****************************************************************************
  1651. ; save_ems_seg  Attempts to save a chunk of RAM to EMS memory
  1652. ;
  1653. ; Entry:        ES points to the segment to save
  1654. ;               AX contains its length (in paragraphs)
  1655. ;               handle holds the EMS handle to write to
  1656. ;               ems_offset holds the 32-bit absolute offset in expanded
  1657. ;                memory to write this block to
  1658. ;
  1659. ; Return:       Carry set on error, carry clear on success
  1660. ;               Updates ems_offset with proper offset for next write
  1661. ; *****************************************************************************
  1662. save_ems_seg    proc    near
  1663.                 push    ds
  1664.                 push    es
  1665.  
  1666.                 assume  ds:@code                ; Tell MASM DS points to our variables
  1667.  
  1668. ; Call the EMS copy memory function to do this; fill in the eMS request block
  1669. ems_write_size: mov     bx, 10h                     ; AX = # of paragraphs
  1670.                 mul     bx                          ; DX:AX = AX * 10h, convert paragraphs to bytes
  1671.                 mov     wptr EMS_size, ax           ; Store # of bytes to write
  1672.                 mov     wptr EMS_size + 2, dx
  1673.  
  1674. ems_write_from: xor     bx, bx
  1675.                 mov     bptr EMS_from, bl           ; Copying from conventional memory (0)
  1676.                 mov     wptr EMS_from_h, bx         ; Source handle is 0 (conventional memory)
  1677.                 mov     wptr EMS_from_o, bx         ; Source offset is 0
  1678.                 mov     ax, es                      ; Segment of source address is ES
  1679.                 mov     wptr EMS_from_s, ax
  1680.  
  1681. ems_write_to:   mov     bptr EMS_to, 1              ; Copying to expanded memory
  1682.                 mov     ax, wptr handle
  1683.                 mov     wptr EMS_to_h, ax           ; Specify EMS handle
  1684.  
  1685.                 ; 32-bit absolute offset for copy is in ems_offset
  1686.                 ;  convert to EMS page:offset (16K pages) values
  1687.                 mov     ax, wptr ems_offset         ; Load 32-byte offset
  1688.                 mov     dx, wptr ems_offset + 2
  1689.                 mov     bx, ax                      ; Save a copy of ax (low 16 bits)
  1690.                 and     ax, 0011111111111111b       ; Get (ax & (16K - 1)), this is the offset (14 bits)
  1691.                 mov     wptr EMS_to_o, ax           ; Save page offset
  1692.                 mov     cl, 14
  1693.                 shr     bx, cl                      ; Move low 2 bits of page into low 2 bits of bx
  1694.                 mov     cl, 2
  1695.                 shl     dx, cl                      ; Move hi ? bits of page into dx shl 2
  1696.                 or      dx, bx                      ; DX = page number (combine two values)
  1697.                 mov     wptr EMS_to_s, dx           ; Save
  1698.  
  1699.                 mov     ax, wptr EMS_size           ; Retrieve size of copy
  1700.                 mov     dx, wptr EMS_size + 2
  1701.                 add     wptr ems_offset, ax         ; Update EMS copy pointer
  1702.                 adc     wptr ems_offset + 2, dx     ;  for next EMS write
  1703.  
  1704. do_ems_write:   mov     si, offset @code:EMS_struc  ; DS:SI -> EMS request structure
  1705.                 mov     ax, 5700h                   ; Function 57 (copy/exchange memory), sub 0, copy memory
  1706.                 int     67h                         ; Call EMS manager
  1707.                 or      ah, ah                      ; AH = 0 means success
  1708.                 jnz     save_ems_seg_er             ; Not 0 means error
  1709.  
  1710. save_ems_seg_ok:clc                                 ; Signal success
  1711.                 jmp     short save_ems_seg_ret
  1712.  
  1713. save_ems_seg_er:stc
  1714.  
  1715. save_ems_seg_ret:
  1716.                 pop     es
  1717.                 pop     ds
  1718.                 ret
  1719. save_ems_seg    endp
  1720. ENDIF
  1721. ; *****************************************************************************
  1722.  
  1723.  
  1724. ; *****************************************************************************
  1725. ; save_disk     Attempts to save program to DOS disk file
  1726. ;
  1727. ; Entry:        DS points to our variables
  1728. ;
  1729. ; Return:       Carry set on error, carry clear on success
  1730. ;               If successful, updates restore_proc with the address of
  1731. ;               the disk restore routine
  1732. ; *****************************************************************************
  1733. IFDEF USE_DISK
  1734. save_disk       proc    near
  1735.                 push    es
  1736.  
  1737.                 assume  ds:@code                ; Tell MASM DS points to our variables
  1738.  
  1739. creat_file:     mov     dx, offset @code:fname  ; DS:DX -> file name
  1740.                 mov     ah, 3Ch                 ; Create/truncate file
  1741.                 mov     cx, 02h                 ; Create a hidden file
  1742.                 int     21h                     ; Call DOS
  1743.                 jc      save_disk_er            ; Carry set, couldn't create file
  1744.  
  1745. creat_ok:       mov     wptr handle, ax         ; Save handle returned by DOS
  1746.  
  1747. ; First, attempt to save the portion of the program block
  1748. disk_prog_save: mov     ax, wptr start_seg      ; Released segment address
  1749.                 mov     es, ax
  1750.                 mov     ax, wptr prog_size      ; Size (in paragraphs) of program block
  1751.                 call    save_disk_seg           ; Attempt to save the program block
  1752.                 jc      disk_write_er           ; Carry flag set == error
  1753.  
  1754. IFNDEF NOFRAG
  1755. ; Next, save the extra DOS segments
  1756. disk_extra_save:
  1757.                 mov     cx, wptr extra_count    ; Number of extra blocks to save
  1758.                 jcxz    save_disk_ok            ; If CX = 0, we exit routine
  1759.  
  1760.                 mov     di, offset dos_blocks   ; DI -> array of segment/size pairs
  1761.  
  1762. disk_extra_save_loop:
  1763.                 mov     ax, wptr [di].block_seg
  1764.                 mov     es, ax                  ; ES = segment to save
  1765.                 mov     ax, wptr [di].block_size; AX = size in paragraphs
  1766.                 push    cx
  1767.                 push    di
  1768.                 call    save_disk_seg           ; Attempt to save this block
  1769.                 pop     di
  1770.                 pop     cx
  1771.                 jc      disk_write_er           ; Carry flag set == error
  1772.                 add     di, size dos_block
  1773.                 loop    disk_extra_save_loop    ; Keep going through all blocks
  1774.  
  1775. ENDIF
  1776.                 jmp     short save_disk_ok
  1777.  
  1778.  
  1779. disk_write_er:  mov     ah, 3Eh                 ; Close file first
  1780.                 mov     bx, wptr handle
  1781.                 int     21h
  1782.                 stc
  1783.                 jmp     short save_disk_ret
  1784.  
  1785.  
  1786. save_disk_ok:   mov     ah, 3Eh                 ; 3eh = close file
  1787.                 mov     bx, wptr handle
  1788.                 int     21h
  1789.                 mov     wptr restore_proc, offset @code:restore_disk    ; Initialize pointer
  1790.                 clc                                                     ;  to restore routine
  1791.                 jmp     short save_disk_ret
  1792.  
  1793. save_disk_er:   stc
  1794.  
  1795. save_disk_ret:  pop     es
  1796.                 ret
  1797. save_disk       endp
  1798.  
  1799.  
  1800. ; *****************************************************************************
  1801. ; save_disk_seg Attempts to save a chunk of RAM to DOS disk file
  1802. ;
  1803. ; Entry:        ES points to the segment to save
  1804. ;               AX contains its length (in paragraphs)
  1805. ;               handle holds the file handle to write to
  1806. ;
  1807. ;
  1808. ; Return:       Carry set on error, carry clear on success
  1809. ; *****************************************************************************
  1810. save_disk_seg   proc    near
  1811.                 push    ds
  1812.                 push    es
  1813.                 push    di
  1814.  
  1815.                 assume  ds:@code
  1816.  
  1817.                 mov     wptr paras_left, ax     ; Used to count paras written
  1818.                 mov     bx, es
  1819.                 mov     ds, bx                  ; DS -> segment to write
  1820.  
  1821.                 assume  ds:nothing
  1822.  
  1823. disk_write_32k: cmp     ax, 0800h               ; paras_left less than 32K?
  1824.                 jb      finish_disk_write       ; Yes, exit
  1825.                 sub     wptr cs:paras_left, 800h; We will write 32K bytes now
  1826.  
  1827.                 mov     ah, 40h                 ; DOS function to write to file
  1828.                 mov     bx, wptr cs:handle      ; BX = file handle to write to
  1829.                 mov     cx, 8000h               ; Write 32K bytes
  1830.                 xor     dx, dx                  ; DS:DX is buffer to write
  1831.                 int     21h                     ; Write data to file
  1832.                 jc      save_disk_seg_er        ; This write failed--escape
  1833.  
  1834. disk_write_ok:  mov     ax, ds                  ; Move write pointer in memory
  1835.                 add     ax, 800h                ; We just wrote 1K paragraphs
  1836.                 mov     ds, ax
  1837.                 mov     ax, wptr cs:paras_left  ; AX checked above
  1838.                 jmp     short disk_write_32k    ; Loop on next 32K
  1839.  
  1840. finish_disk_write:
  1841.                 mov     cl, 4                   ; AX = # paragraphs left to write
  1842.                 shl     ax, cl                  ; Paragraphs to bytes
  1843.                 mov     cx, ax
  1844.                 mov     ah, 40h                 ; 40h = write to file
  1845.                 mov     bx, wptr cs:handle      ; BX = file handle to write to
  1846.                 xor     dx, dx                  ; DS:DX = buffer
  1847.                 int     21h                     ; Call DOS
  1848.                 jc      save_disk_seg_er        ; Carry set, error (close file first)
  1849.  
  1850. save_disk_seg_ok:
  1851.  
  1852.                 clc
  1853.                 jmp     short save_disk_seg_ret
  1854.  
  1855. save_disk_seg_er:
  1856.                 stc
  1857.  
  1858. save_disk_seg_ret:
  1859.                 pop     di
  1860.                 pop     es
  1861.                 pop     ds
  1862.  
  1863.                 ret
  1864. save_disk_seg   endp
  1865.  
  1866.  
  1867.  
  1868. ENDIF
  1869. ; *****************************************************************************
  1870.  
  1871. END
  1872.  
  1873.  
  1874.  
  1875.